module vml

enum State {
	next
	tag_open
	tag_close
	prolog
	comment
	attribute_name
	attribute_quote
	attribute_value
	next_attribute
	content
	cdata
}

pub struct Node {
mut:
	children []Node = []Node{}
	fields map[string]string = map[string]string{}
	name string
	value string
}

pub struct XML {
	text string
pub:
	root Node
}

pub fn to_xml(text string) ?XML {
	node, _ := read_node(text, 0) ?
	// pos, pos2 := get_begin_mark(text, 0) ?
	// println(pos)
	// println(pos2)
	return XML{ text: text, root: node }
}

fn read_node(text string, start int) ?(Node, int) {
	mut state := State.next // init state is next
	mut pre_state := State.next
	mut index := start
	mut name := ''
	mut value := ''
	mut node := Node{}
	for index < text.len {
		inside := state == State.content || state == State.tag_open || state == State.tag_close || 
			state == State.attribute_name || state == State.attribute_value || state == State.cdata
		mut char := ` `
		for index < text.len {
			char = text[index]
			if !inside && char == ` ` {
				index++
			} else {
				break
			}
		}

		match state {
			.next {
				if char == `<` {
					state = State.tag_open
					name = ''
				} else {
					return error('invalid format')
				}
			}
			.tag_open {
				match char {
					`?` {
						if text[index - 1] == `<` {
							state = State.prolog
						} else {
							name += char.str()
						}
					}
					`!` {
						if index + 2 < text.len && text[index - 1] == `<` && 
							text[index + 1] == `-` && text[index + 2] == `-` {
							state = State.comment
							pre_state = State.next
						} else {
							name += char.str()
						}
					}
					`/` {
						node.name = name
						state = State.tag_close
					}
					`>` {
						node.name = name
						state = State.content
					}
					` ` {
						node.name = name
						state = State.attribute_name
						name = ''
					}
					else {
						name += char.str()
					}
				}
			}
			.tag_close {
				if char == `>` { 
					return node, index
				}
			}
			.attribute_name {
				match char {
					`/` {
						state = State.tag_close
					}
					`=` {
						state = State.attribute_quote
					} 
					else {
						if char != ` ` {
							name += char.str()
						}
					}
				}
			}
			.attribute_quote {
				if char == `"` {
					state = State.attribute_value
					value = ''
				} else {
					return error('invalid format')
				}
			}
			.attribute_value {
				if char == `"` {
					node.fields[name] = value
					value = ''
					state = State.next_attribute
				} else {
					value += char.str()
				}
			}
			.next_attribute {
				if char == `>` {
					if text[index - 1] == `/` {
						return node, index
					}
					state = State.content
				} else {
					if char != `/` {
						name = char.str()
						state = State.attribute_name
					}
				}
			}
			.prolog {
				if char == `>` {
					state = State.next
				}
			}
			.comment {
				if char == `>` && text[index - 1] == `-` && text[index - 2] == `-` {
					state = pre_state
				}
			}
			.cdata {
				if char == `]` && text[index - 1] == `]` && index + 1 < text.len && text[index + 1] == `>` {
					state = State.content
					index++
				} else {
					value += char.str()
				}
			}
			.content {
				if char == `<` {
					if text[index..].starts_with('<![CDATA[') {
						state = State.cdata
						index += 8
					} else if index + 1 < text.len && text[index + 1] == `/` {
						state = State.tag_close
						node.value += value
					} else if index + 3 < text.len && text[index + 1] == `!` && 
						text[index + 2] == `-` && text[index + 3] == `-` {
						state = State.comment
						pre_state = State.content
						index += 3
					} else {
						child, end := read_node(text, index) ?
						node.children << child
						index = end
					}
				} else {
					value += char.str()
				}
			}
		}

		index++
	}

	return node, index
}

fn get_begin_mark(text string, start int) ?(int, int) {
	mut begin := -1
	mut end := -1
	mut index := start
	for index < text.len {
		if text[index] == `<` {
			if begin >= 0 {
				return error('invalid format')
			}
			begin = index
		}
		else if text[index] == `>` {
			if begin < 0 {
				return error('invalid format')
			}
			end = index
			return begin, end
		}
		index++
	}
	return begin, end
}